package ms.unicorn.dsl.impl;

import java.lang.ref.WeakReference;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import ms.unicorn.EPhantomAttribute;
import ms.unicorn.Element;
import ms.unicorn.Entity;
import ms.unicorn.NotificationService;
import ms.unicorn.Relation;
import ms.unicorn.UnicornService;
import ms.unicorn.dsl.HintedSpecializationType;
import ms.unicorn.dsl.IEditHelperAdviceEx;
import ms.unicorn.dsl.IEditPartAware;
import ms.unicorn.dsl.IElementField;
import ms.unicorn.dsl.IFigureEx;
import ms.unicorn.dsl.ILanguageDiagram;
import ms.unicorn.dsl.ILanguageElement;
import ms.unicorn.dsl.ILanguageObject;
import ms.unicorn.dsl.IModelLanguage;
import ms.unicorn.dsl.ITheme;
import ms.unicorn.dsl.LanguageRegistry;
import ms.unicorn.dsl.runtime.ExecutionContext;
import ms.unicorn.dsl.runtime.SphynxInterpreter;
import ms.unicorn.dsl.runtime.VariableStorage;
import ms.unicorn.dsl.runtime.interpreter.XFunctionDeclartationInterpreter;
import ms.unicorn.dsl.unicornDSL.NamedProperty;
import ms.unicorn.dsl.unicornDSL.XFeatureCall;
import ms.unicorn.dsl.unicornDSL.XFunctionDeclaration;
import ms.unicorn.dsl.unicornDSL.XReturnStatement;
import ms.unicorn.dsl.unicornDSL.impl.LanguageElementImpl;
import ms.unicorn.dsl.utils.KeyList;
import ms.unicorn.dsl.utils.ReflectionUtil;
import ms.unicorn.dsl.utils.SphynxRuntimeException;
import ms.unicorn.util.NamedMemberProvider;

import org.eclipse.draw2d.IFigure;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.util.ResourceLocator;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.impl.ENotificationImpl;
import org.eclipse.emf.edit.provider.ComposeableAdapterFactory;
import org.eclipse.emf.edit.provider.IItemPropertyDescriptor;
import org.eclipse.emf.edit.provider.ItemPropertyDescriptor;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gmf.runtime.diagram.core.preferences.PreferencesHint;
import org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editpolicies.NonResizableEditPolicyEx;
import org.eclipse.gmf.runtime.emf.type.core.ElementTypeRegistry;
import org.eclipse.gmf.runtime.emf.type.core.IElementType;
import org.eclipse.gmf.runtime.emf.type.core.IHintedType;
import org.eclipse.gmf.runtime.emf.type.core.ISpecializationType;
import org.eclipse.gmf.runtime.emf.type.core.edithelper.IEditHelperAdvice;
import org.eclipse.gmf.runtime.notation.View;
import org.eclipse.jface.resource.ImageDescriptor;

public class ModelLanguageElement extends LanguageElementImpl implements ILanguageDiagram, NamedMemberProvider {
	
	public class RelationConstraint {

		private KeyList<?, ?> keylist;
		private List<ILanguageElement> sources;
		private List<ILanguageElement> targets;
		private WeakReference<Element> cachedSource;
		private WeakReference<Element> cachedTarget;
		private boolean allowed = false;
		private Object allowExp = VariableStorage.UNEVALUATED;
		private String style;
		private ITheme theme;

		public RelationConstraint(KeyList<?, ?> obj) {
			this.keylist = obj;
		}
		
		public String getName() {
			return (String) keylist.getByKey("name");
		}

		public List<ILanguageElement> getSources() {
			if (this.sources == null) {
				sources = new ArrayList<ILanguageElement>();
				Object s = keylist.getByKey("source");
				if (s instanceof ILanguageElement) sources.add((ILanguageElement) s);
				if (s instanceof Object[])
					for (Object item: (Object[])s)
						if (item instanceof ILanguageElement) sources.add((ILanguageElement) item);
			}
			return this.sources;
		}
		
		public List<ILanguageElement> getTargets() {
			if (this.targets == null) {
				targets = new ArrayList<ILanguageElement>();
				Object s = keylist.getByKey("target");
				if (s instanceof ILanguageElement) targets.add((ILanguageElement) s);
				if (s instanceof Object[])
					for (Object item: (Object[])s)
						if (item instanceof ILanguageElement) targets.add((ILanguageElement) item);
			}
			return this.targets;
		}
		
		public Object getAllowExpression() {
			if (allowExp == VariableStorage.UNEVALUATED) {
				allowExp = keylist.getByKey("allow");
			}
			return this.allowExp;
		}


		public boolean isAllowed(Element source, Element target) {
			if (getAllowExpression() == null) return true;
			if (getAllowExpression() instanceof Boolean) return (boolean) getAllowExpression();
			if (getAllowExpression() instanceof XFunctionDeclaration) {
				//if (cachedSource != null && cachedSource.get() == source && cachedTarget != null && cachedTarget.get() == target)
				//	return allowed;
				
				if (source != null && target != null) {				
					cachedSource = new WeakReference<Element>(source);
					cachedTarget = new WeakReference<Element>(target);
					
					ExecutionContext context = getLanguage().getContext().createChildContext();
					context.setThisObject(this);
					XFunctionDeclaration func = (XFunctionDeclaration) getAllowExpression();
					if (func.getArguments().size() != 3)
						throw new SphynxRuntimeException(context, func, String.format("Function signature mismatch. Expected function(constraint, source, target)."), null);
					
					context.allocateVariable(VariableStorage.ARGUMENT, false, func.getArguments().get(0).getName(), this);
					context.allocateVariable(VariableStorage.ARGUMENT, false, func.getArguments().get(1).getName(), source);
					context.allocateVariable(VariableStorage.ARGUMENT, false, func.getArguments().get(2).getName(), target);
					context.setThisObject(ModelLanguageElement.this);
					
					Object res = XFunctionDeclartationInterpreter.interpret(ModelLanguage.interpreter, (XFunctionDeclaration) getAllowExpression(), context);
					if (res instanceof Boolean) 
						allowed = (boolean) res;
				}
				else 
					allowed = false;
			}
			return allowed;
		}
		
		public boolean isAllowed() {
			if (getAllowExpression() == null) return true;
			if (getAllowExpression() instanceof Boolean) return (boolean) getAllowExpression();
			return false;
		}
		
		public String getStyle() {
			if (this.style == null) {
				Object s = keylist.getByKey("style");
				if (style == null) style = "default"; else style = s.toString();
			}
			return this.style;
		}

		public ITheme getTheme() {
			if (this.theme == null) {
				theme = (ITheme) keylist.getByKey("Theme");
			}
			return theme != null ? theme : Theme.DEFAULT;
		}

	}
		
	private String text;
	private String extension;
	private IModelLanguage language;
	private String description;
	private ArrayList<IPalette> palettes;
	private ISpecializationType elementType;
	private IEditHelperAdviceEx advisor;
	private Map<String, IElementField> fields;
	private EClass eClass;
	private List<IItemPropertyDescriptor> propertyDescriptors;
	private List<RelationConstraint> constraints;
	private List<IElementType> containableTypes;
	private ImageDescriptor smallImage;
	private Object iconURL = VariableStorage.UNEVALUATED;
	private Object largeIconURL = VariableStorage.UNEVALUATED;
	private ImageDescriptor largeImage;
	private List<IElementType> relTypesOnSource;
	private Map<ILanguageElement, List<IElementType>> relTypesOnSourceAndTarget = new HashMap<ILanguageElement, List<IElementType>>();
	private Map<ILanguageElement, List<IElementType>> relTypesForTarget = new HashMap<ILanguageElement, List<IElementType>>();
	private List<IElementType> relTypesOnTarget;
	
	public IModelLanguage getLanguage() {
		if (language == null) {
			for (EObject lang = this.eContainer(); lang != null; lang = lang.eContainer())
				if (lang instanceof ModelLanguage) 
					return language = (IModelLanguage)lang;
		}
		return language;
	}
	
	@SuppressWarnings({ "unchecked" })
	public <T> T getElementProperty(String name, T defvalue, ExecutionContext context) {
		// first look for property name in this model element
		for (EObject eprop: this.getProperties()) 
			if (eprop instanceof NamedProperty) {
				NamedProperty prop = (NamedProperty) eprop;
				if (prop.getName().equalsIgnoreCase(name)) {
					Object obj = getLanguage().evaluate(prop.getValue(), context);
					//if (obj instanceof XFunctionDeclaration)
					//	obj = XFunctionDeclartationInterpreter.interpret(SphynxInterpreter.INSTANCE, (XFunctionDeclaration)obj, context);
					if (obj instanceof XReturnStatement) {
						XReturnStatement smt = (XReturnStatement) obj;
						if (smt.getValue() != null)
							obj = SphynxInterpreter.INSTANCE.evaluate(smt.getValue(), context);
						else
							obj = null;
					}
					return (T)obj;
				}
			}
		// then look for this property in the parent element if any.
		if (this.getInheritance() instanceof ILanguageElement) 
			return ((ILanguageElement) this.getInheritance()).getElementProperty(name, defvalue, context);
		// still not found? return the defvalue.
		return defvalue;
	}
	
	public <T> T getElementProperty(String name, T defvalue) {
		return getElementProperty(name, defvalue, getLanguage().getContext());
	}

	@Override
	public String getText() {
		if (text == null)
			text = getElementProperty(PROP_TEXT, getName());
		return text;
	}

	@Override
	public int getObjectType() {
		if ("entity".equals(getType()))
			return LANGUAGE_ENTITY;
		if ("relation".equals(getType()))
			return LANGUAGE_RELATION;
		return LANGUAGE_DIAGRAM;
	}

	@Override
	public String getExtension() {
		if (extension == null) {
			extension = getElementProperty(EXTENSION, getLanguage().getDefaultDiagramExtension());
		}
		return extension;
	}

	@Override
	public IElementType getIElementType() {
		return elementType != null ? elementType : (elementType = createElementType());
	}
	
	protected ISpecializationType createElementType() {
		switch (getObjectType()) {
		case LANGUAGE_ENTITY:
		case LANGUAGE_RELATION:
			HintedSpecializationType result = (HintedSpecializationType) ElementTypeRegistry.getInstance().getType(getId());
			IElementType baseType = getBaseType();
			if (result == null)
					result = new HintedSpecializationType(
						getId(), getIconURL(), getName(), 
						new IElementType[] {baseType},
						null, 
						null, 
						getEditHelperAdvice());
			result.setElement(this);
			result.setLanguage(language);
			result.setSemanticHint(((IHintedType) baseType).getSemanticHint());
			return result;
		default:
			return null;
		}
		
	}

	private IEditHelperAdvice getEditHelperAdvice() {
		if (advisor == null) {
			String defaultAdvisor = "";
			switch(getObjectType()) {
			case LANGUAGE_ENTITY:
				defaultAdvisor = "ms.unicorn.diagram.edit.helpers.LanguageEntityAdvice";
				break;
			case LANGUAGE_RELATION:
				defaultAdvisor = "ms.unicorn.diagram.edit.helpers.LanguageRelationAdvice";
				break;
			}
			String className = getElementProperty("helper", defaultAdvisor);
			advisor = ReflectionUtil.getSingletonObject(className);
		}
		return advisor;
	}

	protected IElementType getBaseType() {
		switch(getObjectType()) {
		case LANGUAGE_ENTITY:
			return ElementTypeRegistry.getInstance().getType("ms.unicorn.diagram.Entity_2001"); //UnicornElementTypes.Entity_2001;
		case LANGUAGE_RELATION:
			return ElementTypeRegistry.getInstance().getType("ms.unicorn.diagram.Relation_4001"); // UnicornElementTypes.Relation_4001;
		}
		return null;
	}
	
	@Override
	public String getTooltip() {
		if (description == null)
			description = getElementProperty(PROP_DESCRIPTION, getText());
		return description;
	}

	static class SimplePalette implements IPalette {
		private Collection<ILanguageElement> elements;
		private String name;

		@SuppressWarnings("unchecked")
		public SimplePalette(Map.Entry<Object, Object> e) {
			name = e.getKey().toString();
			Object val = e.getValue();			
			if (val instanceof Collection<?>)
				elements = (Collection<ILanguageElement>) val;
			else if (val instanceof Object[]) {
				elements = new ArrayList<ILanguageElement>();
				for (Object value: (Object[])val)
					if (value instanceof ILanguageElement) 
						elements.add((ILanguageElement) value);
			}
			else 
				elements = new ArrayList<ILanguageElement>();
			
		}
		
		@Override
		public String getText() {
			return getName();
		}

		@Override
		public String getName() {
			return name;
		}

		@Override
		public Collection<ILanguageElement> getElements() {
			return elements;
		}
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public Collection<IPalette> getPalettes() {
		if (palettes == null) {
			palettes = new ArrayList<IPalette>();
			Object pal = getElementProperty(PALETTE, null);
			if (pal instanceof KeyList<?, ?>) {
				for(Map.Entry<Object, Object> palette: (KeyList<Object, Object>)pal) {
					palettes.add(new SimplePalette(palette));
				}
			}
		}
		return palettes;
	}

	
	@Override
	public String getId() {
		return getLanguage().getId() + "." +  getName();
	}

	
	@Override
	public IEditHelperAdviceEx getAdvisor() {
		return advisor;
	}

	@Override
	public Collection<IElementField> getFields() {
		if (fields == null) {
			fields = new HashMap<String, IElementField>();
			// copy all fields from parent
			if (this.getInheritance() instanceof ILanguageElement) {
				ILanguageElement inheritance = (ILanguageElement) this.getInheritance();
				for(IElementField field: inheritance.getFields())
					fields.put(field.getName(), field);
			}
			
			for (EObject obj: getProperties()) 
				if (obj instanceof IElementField) {
					IElementField f = (IElementField)obj;
					fields.put(f.getName(), f);
				}
		}
		return fields.values();
	}

	@Override
	public IElementField getField(String name) {
		if (getFields() != null)
			fields.get(name);
		return null;
	}
	
	
	@Override
	public EClass getElementEClass() {
		if (eClass == null) {
			EPackage unicornPackage = EPackage.Registry.INSTANCE.getEPackage("http://ms.unicorn");
			
			switch (getObjectType()) {
			case LANGUAGE_ENTITY: eClass = ReflectionUtil.getPropertyValue(unicornPackage, "Entity", null);
				break;
			case LANGUAGE_RELATION: eClass = ReflectionUtil.getPropertyValue(unicornPackage, "Relation", null);
				break;
			}
		}
		return eClass;
	}

	@Override
	public Class<?> getElementClass() {
		switch (getObjectType()) {
		case LANGUAGE_ENTITY: return Entity.class;
		case LANGUAGE_RELATION: return Relation.class;
		}
		return null;
	}
	
	@Override
	public List<IItemPropertyDescriptor> getPropertyDescriptors(ComposeableAdapterFactory composeableAdapterFactory, 
			ResourceLocator resourceLocator, List<IItemPropertyDescriptor> list) {
		if (propertyDescriptors ==  null) {
			propertyDescriptors = new ArrayList<IItemPropertyDescriptor>(list);
			
			for(IElementField field: getFields()) {
					EPhantomAttribute attr = field.getAttribute(); 
					propertyDescriptors.add	(new ItemPropertyDescriptor
						(composeableAdapterFactory,
						 resourceLocator,
						 attr.getDisplayName(),
						 attr.getDescription(),
						 attr,
						 attr.isChangeable(),
						 attr.isMultiLine(),
						 attr.isSortChoice(),
						 attr.getImage(),
						 attr.getCategory(),
						 null));
			}
				
		}
		return propertyDescriptors;
	}

	@Override
	public Object getMember(String name) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void setMember(String name, Object value) {
		// do nothing
	}

	@Override
	public boolean isWritable() {
		return false;
	}

	@Override
	public boolean isInitialized() {
		return true;
	}

	@Override
	public boolean hasMember(String name) {
		return false;
	}

	@Override
	public View createView(View containerView, Element domainElement,
			PreferencesHint preferencesHint) {
		ExecutionContext context = getLanguage().getContext().createChildContext();
		context.allocateVariable(VariableStorage.READONLY, false, "element", domainElement);
		context.allocateVariable(VariableStorage.READONLY, false, "hint", preferencesHint);
		return getElementProperty(ILanguageObject.PROP_VIEW, null, context);
	}

	@Override
	public IFigure createFigure(Element domainElement, EditPart entityEditPart) {
		ExecutionContext context = this.getLanguage().getContext().createChildContext();
		context.setThisObject(this);
		context.allocateVariable(VariableStorage.READONLY, false, "element", domainElement);
		context.allocateVariable(VariableStorage.READONLY, false, "editpart", entityEditPart);
		IFigure figure = getElementProperty(ILanguageObject.PROP_FIGURE, null, context);
		
		if (getObjectType() == LANGUAGE_RELATION && figure instanceof IFigureEx) { // create decorators for connector			
			Relation relation = (Relation) domainElement;
			RelationConstraint constraint = resolveConstraint(relation.getSource(), relation.getTarget());
			((IFigureEx)figure).setTheme(constraint.getTheme());
		}
		
		if (figure instanceof IEditPartAware)
			((IEditPartAware) figure).setEditPart(entityEditPart);
		
		return figure;
	}

	@Override
	public EditPart createEditPart(View view) {
		String type = view.getType();
		if (type.length() > 0) {
			ExecutionContext context = this.getLanguage().getContext().createChildContext();
			context.setThisObject(this);
			context.allocateVariable(VariableStorage.READONLY, false, "view", view);
			return getElementProperty(type, null, context);
		}
		return null;
	}
	
	public List<RelationConstraint> getConstraints() {
		if (constraints == null) {
			constraints = new ArrayList<RelationConstraint>();
			Object obj = getElementProperty(ILanguageObject.PROP_CONSTRAINTS, null);
			if (obj instanceof KeyList<?, ?>)
				constraints.add(new RelationConstraint((KeyList<?, ?>) obj));
			else if (obj instanceof Object[])
				for (Object item: (Object[])obj)
					if (item instanceof KeyList<?,?>)
						constraints.add(new RelationConstraint((KeyList<?, ?>) item));
		}
		return constraints;
	}
	
	public RelationConstraint resolveConstraint(ILanguageElement src, ILanguageElement tar) {
		if (src == null && tar == null)
			return null;
		
		RelationConstraint constraint = null;
		for (RelationConstraint c: getConstraints()) {
			if ( (src == null || c.getSources().contains(src)) && 
				 (tar == null || c.getTargets().contains(tar)) ) {
				if (c.isAllowed()) return c;
				else constraint = c;
			}
		}
		return constraint;
	}
	
	public RelationConstraint resolveConstraint(Element source, Element target) {
		ILanguageElement src = LanguageRegistry.INSTANCE.getLanguageElement(source);
		ILanguageElement tar = LanguageRegistry.INSTANCE.getLanguageElement(target);
		
		if (src == null || tar == null)
			return null;
		
		RelationConstraint constraint = null;
		for (RelationConstraint c: getConstraints()) {
			if (c.getSources().contains(src) && c.getTargets().contains(tar)) {
				if (c.isAllowed(source, target)) return c;
				else constraint = c;
			}
		}
		return constraint;
	}

	@Override
	public boolean checkRelationConstraints(Element container,
			Relation linkInstance, Element source, Element target) {
		ILanguageElement src = LanguageRegistry.INSTANCE.getLanguageElement(source);
		ILanguageElement tar = LanguageRegistry.INSTANCE.getLanguageElement(target);
		
		if (src == null || tar == null || source == target)
			return true;
		
		RelationConstraint constraint = resolveConstraint(source, target);
				
		return constraint != null && constraint.isAllowed(source, target);
	}

	@Override
	public void createDefaultEditPolicies(IGraphicalEditPart editPart,	Element domainElement) {
		ExecutionContext context = this.getLanguage().getContext().createChildContext();
		context.setThisObject(this);
		context.allocateVariable(VariableStorage.READONLY, false, "editpart", editPart);
		context.allocateVariable(VariableStorage.READONLY, false, "element", domainElement);
		Object policyObj = getElementProperty("policies", null, context);
		
		if (policyObj instanceof EditPolicy)
			editPart.installEditPolicy(EditPolicy.GRAPHICAL_NODE_ROLE, (EditPolicy) policyObj);
		else if (policyObj instanceof Object[])
			for (Object item: (Object[])policyObj)
				editPart.installEditPolicy(EditPolicy.GRAPHICAL_NODE_ROLE, (EditPolicy) item);
	}

	@Override
	public EditPolicy createPrimaryDragEditPolicy(Element element, IGraphicalEditPart entityEditPart) {
		boolean resizable = getElementProperty("resizable", true);
		if (!resizable)
			return new NonResizableEditPolicyEx();
		return null;
	}

	@Override
	public void configElement(Element element) {
		ExecutionContext context = getLanguage().getContext().createChildContext();
		context.setThisObject(this);
		context.allocateVariable(VariableStorage.READONLY, false, "element", element);
		Object initObj = getElementProperty("initialize", null);
		if (initObj instanceof XFunctionDeclaration)
			XFunctionDeclartationInterpreter.interpret(ModelLanguage.interpreter, (XFunctionDeclaration) initObj, context);
		
		// broad cast
		UnicornService.getNotificationService().dispatch(this, NotificationService.CHANNEL_ELEMENT, Notification.CREATE, 
				new ENotificationImpl((InternalEObject) element, Notification.CREATE, Notification.NO_FEATURE_ID, element, element));
	}

	@Override
	public List<IElementType> getContainableTypes() {
		if (this.containableTypes == null) {
			containableTypes = new ArrayList<IElementType>();
			Object obj = getElementProperty("containable", null);
			if (obj == null) {
				if (this.getObjectType() == LANGUAGE_DIAGRAM) 
					for (IPalette pal: getPalettes()) 
						for (ILanguageElement e: pal.getElements()) 
							if (e.getObjectType() == LANGUAGE_ENTITY)
								containableTypes.add(e.getIElementType());
			}
			else if (obj instanceof ModelLanguageElement)
				containableTypes.add(((ModelLanguageElement) obj).getIElementType());
			else if (obj instanceof Object[])
				for (Object item: (Object[])obj)
					if (item instanceof ModelLanguageElement)
						containableTypes.add(((ModelLanguageElement) item).getIElementType());
					
		}
		return this.containableTypes;
	}

	@Override
	public ImageDescriptor getSmallImage() {
		if (this.smallImage == null) {
			URL url = getIconURL();
			if (url != null)
				smallImage = ImageDescriptor.createFromURL(url);
		}
		return this.smallImage;
	}

	@Override
	public ImageDescriptor getLargeImage() {
		if (this.largeImage == null) {
			URL url = getLargeIconURL();
			if (url != null)
				largeImage = ImageDescriptor.createFromURL(url);
		}
		return this.largeImage;
	}

	@Override
	public URL getIconURL() {
		if (this.iconURL == VariableStorage.UNEVALUATED) {
			try {
				String file = getElementProperty("icon", null);
				if (file == null)
					iconURL = null;
				else 
					iconURL = new URL("file:" + file);
			} catch (MalformedURLException e) {
				iconURL = null;
			}
		}
		return (URL) this.iconURL;
	}

	@Override
	public URL getLargeIconURL() {
		if (this.largeIconURL  == VariableStorage.UNEVALUATED) {
			try {
				String url = getElementProperty("largeicon", null);
				if (url == null)
					largeIconURL = iconURL;
				else
					largeIconURL = new URL("file:" + url);
			} catch (MalformedURLException e) {
				largeIconURL = iconURL;
			}
		}
		return (URL) this.largeIconURL;
	}

	private static final List<IElementType> EMPTY_LIST = new ArrayList<IElementType>(0);
	
	@Override
	public List<IElementType> getMARelTypesOnSource(IGraphicalEditPart editPart) {
		if (this.relTypesOnSource == null) {
			this.relTypesOnSource = new ArrayList<IElementType>();
			for (ILanguageElement e: getLanguage().getLanguageElements())
				if (e.getObjectType() == LANGUAGE_RELATION)
					if (e.canConnect(this, null)) relTypesOnSource.add(e.getIElementType());
			if (relTypesOnSource.size() == 0)
				relTypesOnSource = EMPTY_LIST;
		}
		return relTypesOnSource;
	}

	@Override
	public List<IElementType> getMARelTypesOnSourceAndTarget(
			IGraphicalEditPart entityEditPart,
			IGraphicalEditPart targetEditPart, ILanguageElement tar) {
		List<IElementType> list = this.relTypesOnSourceAndTarget.get(tar);
		if (list == null) {
			list = new ArrayList<IElementType>();
			for (ILanguageElement e: getLanguage().getLanguageElements())
				if (e.getObjectType() == LANGUAGE_RELATION)
					if (e.canConnect(this, tar)) list.add(e.getIElementType());
			if (list.size() == 0)
				list = EMPTY_LIST;
			relTypesOnSourceAndTarget.put(tar, list);
		}
		return list;
	}

	@Override
	public List<IElementType> getMATypesForTarget(
			IGraphicalEditPart entityEditPart,
			HintedSpecializationType relationshipType) {
		ILanguageElement relation = relationshipType.getElement();
		List<IElementType> list = this.relTypesForTarget.get(relation);
		if (list == null) {
			list = new ArrayList<IElementType>();
			for (ILanguageElement e: getLanguage().getLanguageElements())
				if (e.getObjectType() == LANGUAGE_ENTITY)
					if (!list.contains(e) && relation.canConnect(this, e)) list.add(e.getIElementType());
			if (list.size() == 0)
				list = EMPTY_LIST;
			relTypesForTarget.put(relation, list);
		}
		return list;
	}

	@Override
	public List<IElementType> getMARelTypesOnTarget(
			IGraphicalEditPart entityEditPart) {
		if (this.relTypesOnTarget == null) {
			this.relTypesOnTarget = new ArrayList<IElementType>();
			for (ILanguageElement e: getLanguage().getLanguageElements())
				if (e.getObjectType() == LANGUAGE_RELATION)
					if (e.canConnect(null, this)) relTypesOnTarget.add(e.getIElementType());
			if (relTypesOnTarget.size() == 0)
				relTypesOnTarget = EMPTY_LIST;
		}
		return relTypesOnTarget;
	}

	@Override
	public boolean canConnect(ILanguageElement source, ILanguageElement target) {
		return resolveConstraint(source, target) != null;
	}
}
